var mongoose = require('mongoose');
var util = require('./util.js');
var constants = require('./constants.js');

var dbhost = 'db.304lab.net';
var dbname = 'muchine';

var gridCollectionName = 'opengrid';

var db = mongoose.createConnection(dbhost, dbname);
var logger = util.logger;

//handle db connection event
db.on('error', console.error.bind(console, 'connection error.'));
db.on('open', function() {
	logger.info('connected to ' + dbname + ' database on ' + dbhost);
});

var CellSchema = new mongoose.Schema({
	_id : String,
	gridx : Number,
	gridy : Number,
	username: String,
	key_id: String,
	keyword : String,
	content : String,
	color : String,
	class : String,
	opacity : Number,
	writime : Number,
	alive : Number,
	from : String,
	neighbors : []
});

CellSchema.methods.log = function(message) {
	if (message) logger.info(message);
	logger.info('cell: ' + JSON.stringify(this));
};

CellSchema.methods.post = function(username, keyword, content, color, from, handler) {
	logger.info(this._id + ': posted with keyword ' + keyword);
	
	if (!this.key_id) {
		this.key_id = Number(new Date()) + (Math.floor(Math.random()*1000));
	}
	
	console.log('username: ' + username);
	
	this.username = username;
	this.keyword = keyword;
	this.content = content;
	this.color = color;
	this.class = constants.class.MASTER;
	this.writime = Number(new Date());
	this.alive = this.writime;
	this.from = from;
	
	this.save();
	if (handler) handler();
};

CellSchema.methods.clear = function() {
	this.username = undefined;
	this.key_id = undefined;
	this.keyword = undefined;
	this.color = undefined;
	this.class = undefined;
	this.content = undefined;
	this.opacity = undefined;
	this.writime = undefined;
	this.alive = undefined;
	this.from = undefined;
	this.neighbors = [];
};

CellSchema.methods.remove = function(handler) {
	// this method does not delete physically a row from database
	this.clear();
	this.save();
	
	if (handler) handler();
};

CellSchema.methods.degrade = function(handler) {
	// if master cell, degrade it to slave or empty
	if (this.isMaster()) {
		logger.debug(this._id + ': degraded.');
		
		this.class = constants.class.SLAVE;
		this.refresh(handler);
	}
};

CellSchema.methods.getType = function() {
	if (this.class == constants.class.MASTER)
		return 'master';
	else if (this.class == constants.class.SLAVE)
		return 'slave';
	else
		return 'empty';
};

CellSchema.methods.isMaster = function() {
	if (this.getType() == 'master') return true;
	
	return false;
};

CellSchema.methods.isEmpty = function() {
	if (this.getType() == 'empty') return true;
	
	return false;
};

CellSchema.methods.isRelated = function(cell) {
	if (this.key_id == cell.key_id) return true;
	
	return false;
};

CellSchema.methods.getNearCellIds = function() {
	var ids = [
   	    'g' + (this.gridx + 1) + 'x' + (this.gridy + 1) + 'y',
   	    'g' + (this.gridx + 2) + 'x' + (this.gridy) + 'y',
   	    'g' + (this.gridx + 1) + 'x' + (this.gridy - 1) + 'y',
   	    'g' + (this.gridx - 1) + 'x' + (this.gridy - 1) + 'y',
   	    'g' + (this.gridx - 2) + 'x' + (this.gridy) + 'y',
   	    'g' + (this.gridx - 1) + 'x' + (this.gridy + 1) + 'y'
   	];
   	
   	return ids;
};

CellSchema.methods.hasNeighbor = function() {
	if (this.neighbors) {
		if (this.neighbors.length > 0)
			return true;
	}
	
	return false;
};

CellSchema.methods.addNeighbor = function(neighborId, handler) {
	logger.info(this._id + ': add ' + neighborId + ' to neighbor');
	
	// if neighbor id exists, do nothing
	var index = this.neighbors.indexOf(neighborId);
	if (index >= 0) return;
	
	this.neighbors.push(neighborId);
	this.refresh(handler);
};

CellSchema.methods.removeNeighbor = function(neighborId, handler) {
	logger.info(this._id + ': remove ' + neighborId + ' from neighbor');
	
	// if neighbor id doesn't exist, do nothing
	var index = this.neighbors.indexOf(neighborId);
	if (index < 0) return;
	
	this.neighbors.splice(index, 1);
	this.refresh(handler);
};

CellSchema.methods.refresh = function(handler) {
	logger.info(this._id + ': refresh cell.');
	
	if(this.isMaster()) {
		// if master cell, just save updated information
		this.save();
		if(handler) handler();
	}
	else if (this.hasNeighbor()) {
		var slave = this;
		
		// make this cell to slave of 1st neighbor
		var masterId = this.neighbors[0]; 
		get(masterId, function(master) {
			if (master.isMaster()) {
				logger.debug(slave._id + ': refreshing... get into slave status.');
				
				slave.key_id = master.key_id;
				slave.keyword = master.keyword;
				slave.color = master.color;
				slave.class = constants.class.SLAVE;
				slave.content = undefined;
				slave.writime = undefined;
				slave.from = undefined;
				
				// TODO: to reduce database operation, we should check if the data is updated actually
				
				slave.save();
				if(handler) handler();
			}
			else {
				logger.debug(slave._id + ': refreshing... invalid master id - ' + masterId);
				
				// this neighbor cell is invalid, so remove it and refresh again
				slave.removeNeighbor(masterId);
			}
		});
	}
	else {
		logger.debug(this._id + ': refreshing... removed.');
		this.remove(handler);
	}
};

CellSchema.methods.reserve = function(handler) {
	// if master cell, do nothing
	if(this.isMaster()) return;
	
	this.class = constants.class.RESERVED;
	this.save();
	
	handler();
};

CellSchema.methods.release = function(handler) {
	if (this.class != constants.class.RESERVED) return;
	
	// assume that master cannot be reserved.
	this.refresh(handler);
};

var Cell = db.model(gridCollectionName, CellSchema);

/*
 * Cell operations
 */
var create = function(id) {
	var location = util.getLocation(id);
	
	var cell = new Cell({
		_id: id,
		gridx: location.x,
		gridy: location.y
	});
	
	return cell;
};

var get = function(id, handler) {
	Cell.findById(id, function(err, cell) {
		if (err) {
			// TODO: if retrieval failed, consider to add retry logic for transaction
			logger.error('error occured while retrieving object. ' + err);
			cell = null;
		}
		
		if (cell) {
			handler(cell);
		}
		else {
			handler(create(id));
		}	
	});
};

var find = function(condition, fields, restraint, handler) {
	Cell.find(condition, fields, restraint, function(err, cells) {
		if (err) {
			// TODO: add error handler logic
		}
		
		if (handler) handler(cells);
	});
};

exports.get = get;
exports.find = find;